package org.slf4j.impl;

import baseFx.log.FxFilter;
import baseFx.log.FxLog;
import baseFx.log.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.LoggingException;
import org.apache.logging.log4j.core.Layout;
import org.apache.logging.log4j.core.appender.ConsoleAppender;
import org.apache.logging.log4j.core.appender.RollingFileAppender;
import org.apache.logging.log4j.core.appender.rolling.CompositeTriggeringPolicy;
import org.apache.logging.log4j.core.appender.rolling.SizeBasedTriggeringPolicy;
import org.apache.logging.log4j.core.appender.rolling.TimeBasedTriggeringPolicy;
import org.apache.logging.log4j.core.appender.rolling.TriggeringPolicy;
import org.apache.logging.log4j.core.config.AppenderRef;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.LoggerConfig;
import org.apache.logging.log4j.core.layout.JsonLayout;
import org.apache.logging.log4j.core.layout.PatternLayout;
import org.apache.logging.log4j.spi.AbstractLoggerAdapter;
import org.apache.logging.log4j.spi.LoggerContext;
import org.apache.logging.log4j.util.StackLocatorUtil;
import org.apache.logging.slf4j.Log4jLogger;
import org.slf4j.ILoggerFactory;
import org.slf4j.Logger;

import java.io.File;

public class FxLog4jLoggerFactory extends AbstractLoggerAdapter<Logger> implements ILoggerFactory {
    private static final String FQCN = FxLog4jLoggerFactory.class.getName();
    private static final String PACKAGE = "org.slf4j";
    private static final String TO_SLF4J_CONTEXT = "org.apache.logging.slf4j.SLF4JLoggerContext";
    private static FxLog4jLoggerFactory single;

    public static FxLog4jLoggerFactory getInstance() {
        return single;
    }

    public FxLog4jLoggerFactory() {
        single = this;
    }

    @Override
    protected Logger newLogger(final String name, final LoggerContext context) {
        String key = Logger.ROOT_LOGGER_NAME.equals(name) ? LogManager.ROOT_LOGGER_NAME : name;
        LoggerContext ctx = validateContext(context);
        if (ctx instanceof org.apache.logging.log4j.core.LoggerContext) {
            org.apache.logging.log4j.core.LoggerContext core = (org.apache.logging.log4j.core.LoggerContext) ctx;
            add(core, key);
        }
        return new Log4jLogger(ctx.getLogger(key), key);
    }

    @Override
    protected LoggerContext getContext() {
        Class<?> anchor = StackLocatorUtil.getCallerClass(FQCN, PACKAGE);
        return anchor == null ? LogManager.getContext() : getContext(StackLocatorUtil.getCallerClass(anchor));
    }

    private LoggerContext validateContext(final LoggerContext context) {
        if (TO_SLF4J_CONTEXT.equals(context.getClass().getName())) {
            throw new LoggingException("log4j-slf4j-impl cannot be present with log4j-to-slf4j");
        }
        return context;
    }

    static void add(org.apache.logging.log4j.core.LoggerContext context, String name) {
        if (FxLog.isFxLog(name) == false) {
            return;
        }
        Configuration config = context.getConfiguration();
        if (config.getLoggers().containsKey(name)) {
            return;
        }

        Level[] defLv = new Level[]{Level.ERROR, Level.WARN, Level.INFO};
        LoggerConfig conf = LoggerConfig.createLogger(false, org.apache.logging.log4j.Level.ALL, name, "true", new AppenderRef[0], null, config, null);
        Layout<?> layout;
        if (FxLog.useJsonLayout) {
            layout = JsonLayout.newBuilder().setConfiguration(config)
                    .setCompact(true).setLocationInfo(true).setComplete(false).setEventEol(true)
                    .build();
        } else {
            layout = PatternLayout.newBuilder().withConfiguration(config).withPattern(FxLog.msgLayout).build();
        }
        TimeBasedTriggeringPolicy tp = TimeBasedTriggeringPolicy.newBuilder().withInterval(1).build();
        TriggeringPolicy policy = SizeBasedTriggeringPolicy.createPolicy("10M");
        CompositeTriggeringPolicy composite = CompositeTriggeringPolicy.createPolicy(tp, policy);
        String path = FxLog.logDir + File.separator;
        if (FxLog.singleFile == false) {
            path += name.replace(".", File.separator) + File.separator;
        }
        RollingFileAppender fileAppender = RollingFileAppender.newBuilder()
                .setConfiguration(config)
                .setName(name + "_file")
                .setLayout(layout)
                .withFileName(path + "logging.log")
                .withFilePattern(path + "%d{yyyy-MM-dd}.%i.log")
                .withAppend(true)
                .withPolicy(composite)
                .build();
        fileAppender.addFilter(new FxFilter(defLv));
        fileAppender.start();

        ConsoleAppender consoleAppender = ConsoleAppender.newBuilder()
                .setConfiguration(config)
                .setName(name + "_console")
                .setLayout(layout)
                .build();
        consoleAppender.addFilter(new FxFilter(defLv));
        consoleAppender.start();

        conf.addAppender(fileAppender, null, null);
        conf.addAppender(consoleAppender, null, null);

        config.addLogger(name, conf);
        context.updateLoggers();
        if (FxLog.onNew != null) {
            FxLog.onNew.execute(name);
        }
    }
}
